package net.pws.common.security.spi;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Md5PasswordEncoder implements PasswordEncoder {

	protected Log logger = LogFactory.getLog(getClass());

	private boolean encodeHashAsBase64 = true;
	
	private String salt = "";	//CMS

	// ~ Methods
	// ========================================================================================================

	public boolean getEncodeHashAsBase64() {
		return encodeHashAsBase64;
	}

	/**
	 * The encoded password is normally returned as Hex (32 char) version of the
	 * hash bytes. Setting this property to true will cause the encoded pass to
	 * be returned as Base64 text, which will consume 24 characters.
	 * 
	 * @param encodeHashAsBase64
	 *            set to true for Base64 output
	 */
	public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
		this.encodeHashAsBase64 = encodeHashAsBase64;
	}
	
	public String getSalt() {
		return salt;
	}

	public void setSalt(String salt) {
		// if (StringUtils.isEmpty(salt)) {
		// return;
		// }
		this.salt = salt;
	}

	/**
	 * Used by subclasses to extract the password and salt from a merged
	 * <code>String</code> created using
	 * {@link #mergePasswordAndSalt(String,Object,boolean)}.
	 * <p>
	 * The first element in the returned array is the password. The second
	 * element is the salt. The salt array element will always be present, even
	 * if no salt was found in the <code>mergedPasswordSalt</code> argument.
	 * </p>
	 * 
	 * @param mergedPasswordSalt
	 *            as generated by <code>mergePasswordAndSalt</code>
	 * @return an array, in which the first element is the password and the
	 *         second the salt
	 * @throws IllegalArgumentException
	 *             if mergedPasswordSalt is null or empty.
	 */
	protected String[] demergePasswordAndSalt(String mergedPasswordSalt) {
		if ((mergedPasswordSalt == null) || "".equals(mergedPasswordSalt)) {
			throw new IllegalArgumentException(
					"Cannot pass a null or empty String");
		}

		String password = mergedPasswordSalt;
		String salt = "";

		int saltBegins = mergedPasswordSalt.lastIndexOf("{");

		if ((saltBegins != -1)
				&& ((saltBegins + 1) < mergedPasswordSalt.length())) {
			salt = mergedPasswordSalt.substring(saltBegins + 1,
					mergedPasswordSalt.length() - 1);
			password = mergedPasswordSalt.substring(0, saltBegins);
		}

		return new String[] { password, salt };
	}

	/**
	 * Used by subclasses to generate a merged password and salt
	 * <code>String</code>.
	 * <P>
	 * The generated password will be in the form of <code>password{salt}</code>
	 * .
	 * </p>
	 * <p>
	 * A <code>null</code> can be passed to either method, and will be handled
	 * correctly. If the <code>salt</code> is <code>null</code> or empty, the
	 * resulting generated password will simply be the passed
	 * <code>password</code>. The <code>toString</code> method of the
	 * <code>salt</code> will be used to represent the salt.
	 * </p>
	 * 
	 * @param password
	 *            the password to be used (can be <code>null</code>)
	 * @param salt
	 *            the salt to be used (can be <code>null</code>)
	 * @param strict
	 *            ensures salt doesn't contain the delimiters
	 * @return a merged password and salt <code>String</code>
	 * @throws IllegalArgumentException
	 *             if the salt contains '{' or '}' characters.
	 */
	protected static String mergePasswordAndSalt(String password, Object salt,
			boolean strict) {
		if (password == null) {
			password = "";
		}

		if (strict && (salt != null)) {
			if ((salt.toString().lastIndexOf("{") != -1)
					|| (salt.toString().lastIndexOf("}") != -1)) {
				throw new IllegalArgumentException(
						"Cannot use { or } in salt.toString()");
			}
		}

		if ((salt == null) || "".equals(salt)) {
			return password;
		} else {
			return password + "{" + salt.toString() + "}";
		}
	}

	protected static MessageDigest getMessageDigest()
			throws NoSuchAlgorithmException {
		return MessageDigest.getInstance("MD5");
	}

	/**
	     * 
	     */
	private String encodePassword(String rawPass, Object salt) {
		if (logger.isDebugEnabled()) {
			logger.debug("before encode password: " + rawPass);
			logger.debug("before encode salt: " + salt);
		}
		try {
			String saltedPass = mergePasswordAndSalt(rawPass, salt, false);

			MessageDigest messageDigest = getMessageDigest();

			byte[] digest = messageDigest.digest(saltedPass.getBytes());

			if (getEncodeHashAsBase64()) {
				return new String(Base64.encodeBase64(digest));
			} else {
				return new String(Hex.encodeHex(digest));
			}
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	     * 
	     */
//	public static String encodePassword(String rawPass) {
//		try {
//			String saltedPass = mergePasswordAndSalt(rawPass, "", false);
//
//			MessageDigest messageDigest = getMessageDigest();
//
//			byte[] digest = messageDigest.digest(saltedPass.getBytes());
//
//			return new String(Base64.encodeBase64(digest));
//		} catch (NoSuchAlgorithmException e) {
//			throw new RuntimeException(e);
//		}
//	}

	/**
	     * 
	     */
	public boolean isPasswordValid(String encPass, String rawPass) {
		String pass1 = "" + encPass;
		String pass2 = encodePassword(rawPass, salt);

		return pass1.equals(pass2);
	}

	@Override
	public String encode(String password) {
		return encodePassword(password, salt);
	}

}
